/* Version iterativa de lista con header implementadas dinamicamente */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../include/list.h"

/* este es el nodo distinguido header */

struct listCDT
{
    struct nodoCDT * nodos;
    cmpfnT Compara;
    freefnT Libera;
    size_t tam;
    int cant;
    struct nodoCDT * actual;
};

/* este es el formato de cada nodo de la lista con informacion */

typedef struct nodoCDT
{
	listElementT dato; /* donde se guarda la informacion */
	struct nodoCDT * tail;
}nodoCDT;

/* Funciones estaticas */
static void
Error(const char *cartel)
{
// 	fprintf(stderr,cartel);
	exit(EXIT_FAILURE);
}

static nodoCDT *
ListTail(nodoCDT * listInterna)
{
	if (listInterna == NULL)
		Error("Acceso a la cola de una lista vacia\n");

		return listInterna->tail;
}

listADT
NewList(int (*fn) (listElementT elem1, listElementT elem2), void (*libera) (listElementT), size_t tam)
{
	listADT auxi;
	if (fn == NULL)
		return NULL;
	if (tam <= 0)
		return NULL;

	if ( (auxi = malloc(sizeof(struct listCDT))) == NULL )
		Error("No hay lugar disponible\n");

	auxi->nodos = NULL;
	auxi->Compara = fn;
	auxi->Libera = libera;
	auxi->tam = tam;
	auxi->actual = NULL;
	auxi->cant= 0;

	return auxi;
}

void
Insert(listADT list, listElementT element)
{
	nodoCDT *ant, *rec, *auxi;

	ant = rec = list->nodos;

	while (rec != NULL && (*list->Compara)(rec->dato,element) < 0)
	{
		ant = rec;
		rec = ListTail(rec);
	}

	/* ... como no se aceptan repetidos ... */
// 	if (rec != NULL && (*list->Compara)(rec->dato,element) != 0)
	if(rec != NULL && ElementBelongsList(list, element))
	{
// 		fprintf(stderr,"Elemento repetido\n");
		return;
	}

	/* se inserta */
	if ((auxi = malloc(sizeof(struct nodoCDT))) == NULL)
		Error("No hay lugar para otro nodo\n");

	auxi->tail = rec;

	/* genero lugar para copiar la info que recibo indirectamente */
	if ((auxi->dato = malloc(list->tam)) == NULL)
	{
		free(auxi);
		Error("No hay lugar para otro nodo\n");
	}

	memcpy(auxi->dato,element,list->tam);
	list->cant++;


	/* es el primero */
	if (ant == rec)
		list->nodos = auxi;
	else
		ant->tail = auxi;
}

int
Delete(listADT list, listElementT element)
{
	nodoCDT *ant, *rec;

	rec = ant = list->nodos;

	while (rec != NULL && (*list->Compara)(rec->dato,element) == 0)
	{
		ant = rec;
		rec = ListTail(rec);
	}

	/* no lo encontre */
	while (rec == NULL && (*list->Compara)(rec->dato,element) != 0)
	{
		fprintf(stderr,"Elemento no encontrado\n");
		return 0;
	}

	/* a borrarlo */
	if (ant == rec)	/* es el primero */
		list->nodos = ant->tail;
	else			/* es del medio o final */
		ant->tail = rec->tail;
	/* si el elemento a borrar era el indicado por actual debemos mover a actual
	 * para que apunte a un elemento dentro de la lista y no uno borrado */
	if (rec == list->actual)
		list->actual = ListTail(list->actual);

	list->Libera(rec->dato);
	free(rec->dato);
	free(rec);
	list->cant --;

	return 1;
}

int
ListIsEmpty(listADT list)
{
    return (list == NULL || list->nodos == NULL);
}

int
ElementBelongsList(listADT list, listElementT element)
{
	nodoCDT *actual;
	void *dato;
	int rta;

	actual = list->actual;

	if ((dato = malloc(list->tam)) == NULL)
		Error("problemas con memoria\n");

	SetBegin(list);
	while(GetDato(list,dato) && (*list->Compara)(dato,element) == 0 )
		;

	list->actual = actual;

	rta = dato != NULL && (*list->Compara)(dato,element);

	free(dato);

	return rta;
}

void
SetBegin(listADT list)
{
	if( list== NULL)
		return;
	list->actual = list->nodos;
}

void
FreeList(listADT list)
{
	nodoCDT *auxi;

	while (!ListIsEmpty(list))
	{
		auxi = list->nodos;
		list->nodos = auxi->tail;
		list->Libera(auxi->dato);
		free(auxi->dato);
		free(auxi);
	}

	free(list);
}


int
GetDato(listADT list, listElementT element)
{
	if (list==NULL || list->actual == NULL)
		return 0;

	memcpy(element, list->actual->dato, list->tam);
	list->actual = ListTail(list->actual);

	return 1;
}


listElementT
GetDatoByComp(listADT list, listElementT element)
{
	listElementT answer, answer2;
	nodoCDT *actualnode;

	if(list==NULL || list->nodos==NULL)
		return NULL;

	if((answer2=malloc(list->tam))==NULL)
		return NULL;

	actualnode = list->nodos;
	while(actualnode!=NULL)
	{
		answer=actualnode->dato;
		if(list->Compara(answer, element)!=0)
		{
			memcpy(answer2, actualnode->dato, list->tam);
			return answer2;
		}
		actualnode=actualnode->tail;
	}

	free(answer2);
	return NULL;
}

int
GetListCount(listADT list)
{
	if( list ==NULL)
		return 0;
	return list->cant;
}

void
ChangeCompareFunc(listADT list, int (*fn) (listElementT elem1, listElementT elem2))
{
	list->Compara = fn;
	return;
}
